home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Skunkware 98
/
Skunkware 98.iso
/
osr5
/
sco
/
scripts
/
admin
/
nidleout
< prev
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
NeXTSTEP
RISC OS/Acorn
Shift JIS
UTF-8
Wrap
Korn shell script
|
1997-08-26
|
46.3 KB
|
1,290 lines
#!/bin/ksh
# nidleout: new idleout
# @(#) nidleout.ksh 3.5 97/06/18
# 91/10/13 John H. DuBois III (john@armory.com)
# 91/10/20 Made it also check time since last access, using l -u.
# Both time since last access and time since last modify must be
# greater than the idleout time for a user to be idled out.
# This fixes the problem where the user is in an application that
# relies entirely on the line discipline for tty output,
# and never does a write itself (ex in insert mode, mail in compose
# mode), so that the tty modify time is never updated.
# 91/11/27 Changed modem tty pattern examples to *[A-Z]* from *[A-Z].
# 92/02/08 Changed format of IDLETIME to be a list of user patterns and idle
# times, so some users can be given longer idle times than others.
# 92/03/30 Do a ps on ttys for logfile *before* killing off processes.
# 92/04/03 Added '... to <user>' to iwrite message
# eval the test '[[ $user = $pat ]]'; required by new ksh
# 92/05/12 Added -r option
# Changed IDLETIME to IDLETIMES because its format is different.
# 92/05/13 Added SAVEPROCS
# 93/04/06 Don't do a ps if SAVEPROCS is empty
# Ignore ttys with line disc 2 (event driver) set.
# 93/04/12 Added -x flag and some debug code. Let default file values be
# replaced by internal values by giving empty value on cmd line.
# Made 60 minute internal default idleout time.
# Ignore ttys with no line discipline (slip/ppp).
# Made TTYS able to be a ksh pattern.
# Give users a minute to clean up.
# 93/04/28 Do not assume PAGER will be set.
# 93/06/02 exec ksh if not running it.
# 93/07/01 Added ability to do a ps on more than 20 ttys
# 93/08/04 Set PATH explicitly.
# 93/11/17 Invoke ps with full path. Print error if it fails.
# 93/12/31 Added checking for processes using /dev/tty. Added -A option.
# Fixed bug that prevented users from being idled out if modify
# time was 0, regardless of -a.
# 94/03/20 Use " instead of ' around pattern in eval [[ foo = $pattern ]]
# 94/06/17 Fixed bug in code that ignores ttys with no line discipline.
# 94/07/24 New idle time specification format. Optimize various operations.
# Kill write if it hangs. Added p option.
# 94/09/13 Added NOCHECKDISC.
# 95/01/23 Missed some kills that needed stderr redirected.
# 95/05/13 Added date & time to error messages.
# 95/08/25 Work around ksh bug? by not piping idlettys() into a read statement
# 95/12/27 Test whether utmp entries are still active by kill -0'ing their
# procs.
# 95/12/28 More v5 porting: Use who -x if available instead of nwho;
# work with new time format in ps output.
# 96/06/10 Added SIGLIST. Use correct field for tty in ps output in ps_t().
# 96/09/05 Use ditty for stty of Digi ttys. Deal correctly with 5.0 stty
# output that contains both line discipline number & name.
# Only force device name to lower case when opening sio ports.
# 97/06/18 Deal with who output that includes comment from inittab
# todo: Add a category like 'procs', which require that at least the tty
# modify time be touched regularly, for apps that may need to run for a long
# time w/o user input but which will be constantly updating a status.
# e.g. ftp with 'hash' turned on; lynx
# Also, there are some applications (e.g. WordPerfect) that apparently update
# the access time (only) regularly; may be doing non-blocking reads, or perhaps
# reads interrupted by timers for the sake of file syncs and such? An option
# to use the modify time instead of the access time for certain procs would
# fix this. ref#787557
# Notes:
# There could be a problem when using -A: if a user was using some utility
# that used the event driver, or which had /dev/tty open, nidleout would
# ignore the tty, but if the user quit the utility & nidleout checked the
# tty before the user typed anything further, it would find that the access
# time should be trusted & it would be old. But, it appears that the access
# time on ttys is updated not only when returning from a read(), but also
# when entering it(!), so this is not a problem in practice unless this
# behaviour is changed.
if [ -z "$SECONDS" ]; then
exec /bin/ksh -c "$0 $@"
fi
alias istrue="test 0 -ne"
alias isfalse="test 0 -eq"
# iwrite: write from the idleout daemon to a tty
# Usage: iwrite tty message [user]
function iwrite {
# Use the same device that user is logged in on, rather than always using
# the lower case device. Some serial drivers will only let one device be
# opened, and this code avoids hanging.
typeset tty=$1
typeset touser message=$2 user=$3 msg
if [ -w /dev/$tty ]; then
[ $# = 3 ] && touser=" to $user"
msg=\
"\n\r\07\07\07Message from idleout_daemon$touser `date \"+[ %a %h %m %T ]\"` ...
$message\r"
istrue debug && print -u2 "iwrite: Writing$touser $tty:\n$msg"
( print "$msg" > /dev/$tty )&
sleep 5
if kill -9 $! 2>/dev/null; then
ErrMsg "write (pid $!)$touser on $tty hung."
sleep 1
kill -9 $! 2>/dev/null &&
ErrMsg "could not kill write process $!; hung?"
fi
fi
}
function ErrMsg {
print -u2 "$(date '+%D %T') $tl_name: $*"
}
# Usage: hm2min hrs:min
# Converts the time in hrs:min format to a number of minutes and sets
# hm2min_ret to it.
typeset -i hm2min_ret
function hm2min {
typeset IFS=:
set -- $1
hm2min_ret=$1*60+$2
}
# Usage: checkaccess ttyname ...
# checkaccess prints a single line listing the times in minutes since the last
# read from each tty completed. The times are printed in the same order
# as the tty names are given.
function checkaccess {
cd /dev
l -u $* | awk '
BEGIN {
# make sure major & minor #s are read as two words
# even if minor # is 3 digits
FS = "[ ,]+"
}
{
if ($9 ~ ":") {
split($9,hm,":")
acctimes[$10] = hm[1] * 60 + hm[2]
}
else
acctimes[$10] = 24 * 60
}
END {
"date \"+%H %M\"" | getline
curmin = $1 * 60 + $2
num = split(ttys,TTYNames)
for (i = 1; i <= num; i++) {
idle = curmin - acctimes[TTYNames[i]]
if (idle < 0)
idle += 24 * 60
if (idle > 0)
idle -= 1
printf idle " "
}
print ""
}
' ttys="$*"
}
# Usage: TTYMatch user tty
# Return success if user & tty match a rule, and its time is nonzero
function TTYMatch {
typeset -i i=0
while [ i -lt NumRules ]; do
eval [[ \$user = "${R_user[i]}" '&&' \$tty = "${R_tty[i]}" ]] &&
[ ${R_time[i]} -ne 0 ] && return 0
((i+=1))
done
return 1
}
# Usage: MakeTTYList
# MakeTTYList finds ttys that have been idle at least one minute,
# that users are logged in on and that match the tty,user pair of a rule.
# It sets the arrays ttys[], acctimes[], modtimes[], and users[],
# starting with index 0, to each tty name, the number of minutes since it has
# been accessed, the number of since it has been modified, and the user logged
# in on it.
#
# Expect who -u ouput to be in this form:
#spcecdt tty05 Oct 04 01:21 . 84
#rbnhood tty3F Oct 04 13:25 0:27 2918 This is an inittab comment
# When split on space, the fields are:
# 1 2 3 4 5 6 7 8 ...
#rbnhood tty3F Oct 04 13:25 0:27 2918 This is an inittab comment
# Expect who -ux ouput to be in this form:
#root tty01 Dec 26 22:25 old 706
#pax tty1b Dec 27 21:35 13:09 3872 comment
#cdk tty3F Dec 28 12:20 . 26533
#cardinal ttyp0 Dec 28 12:30 gorn.evolve.com . 28045
#ssahin ttyp14 Dec 25 18:42 narwhal.cc.metu.edu.tr 12:34 1152
# When split on space, the fields for lines that include hostname are:
# 1 2 3 4 5 6 7 8
#cardinal ttyp0 Dec 28 12:30 gorn.evolve.com . 28045
#
# Determining the difference between an entry with and without remote hostname:
# With: f6 is a hostname; f7 is old/./#:#; f8 is pid
# Without: f6 is old/./#:#; f7 is pid; f8 may be comment
# Among these, the only that is guaranteed to be discernible is f7, which will
# only consist entirely of a string of digits if it is a pid (no-hostname)
typeset -i modtimes acctimes
function MakeTTYList {
# cannot typeset -i idle because the field may be "." (not idle) or "old"
typeset idle line user tty x=
typeset -i minutes i=0 proc
typeset IFS=' '
unset ttys[*] acctimes[*] modtimes[*] users[*]
# Get modify times of ttys that have been idle at least 1 minute
# and are ttys that idleout should act on
$utmpx && x=x
who -u$x | while read line; do
set -- $line
user=$1 tty=$2
case "$7" in
+([0-9]))
idle=$6 proc=$7
;;
*)
idle=$7 proc=$8
SetHost "$tty" "$6"
;;
esac
# some pty using procs don't clean up after themselves. This is an
# easy but not conclusive test for that.
kill -0 $proc 2>/dev/null 1>&2 || {
istrue debug &&
print -u2 "PID $proc for line $tty does not exist."
continue
}
case $idle in
old) idle=24:00;;
.) idle=0:00;; # don't skip these; might be using acc time not mod
esac
if TTYMatch $user $tty; then
hm2min $idle
minutes=hm2min_ret
ttys[i]=$tty
modtimes[i]=minutes
users[i]=$user
let i+=1
istrue debug &&
print -u2 "From who -u: $user on $tty idle $minutes minute(s)"
fi
done
istrue debug && [ i -eq 0 ] && print -u2 "MakeTTYList: No idle users."
[ i -eq 0 ] && return
# Get tty read times
# Runs l, awk, date once
checkaccess ${ttys[*]} | read line
set -A acctimes $line
}
# Usage: TTYIdle maxtime tty-index access_only modify_backup
# A line is idle for as long as it has not been read from or written to,
# unless <access_only> is true, in which case it is idle for as long as
# it has not been read from (last-written time is ignored.)
# If modify_backup is true, the modify time is used if the tty is using the
# event line discipline, or if a process on the tty has /dev/tty open and is
# not stopped.
# ttys are ignored if they have no line discipline.
# This function takes the global arrays
# ttys[], acctimes[], modtimes[], and users[], applies rules for which of
# modtime & acctime should be used for the tty, ignores ttys in certain
# situations, and assigns the result to TTYIdle_ret
typeset -i TTYIdle_ret
function TTYIdle {
typeset -i ind=$1 maxtime=$2 access_only=$3 modify_backup=$4 LineDisc
typeset -i minutes atime=${acctimes[ind]} mtime=${modtimes[ind]}
typeset deb
istrue debug &&
deb="${ttys[ind]}: atime=$atime,mtime=$mtime: "
# if user is not idle, return
if [ mtime -eq 0 -a atime -eq 0 ]; then
TTYIdle_ret=0
return
fi
# if mod & acc time are the same, no need for line disc info to decide
# between them; and if they are less than allowed time, no need to know
# if there is any line disc at all, so skip it
if [[ ( mtime -eq atime ) && ( mtime -lt maxtime ) ]]; then
TTYIdle_ret=mtime
return
fi
ChkLineDisc ${ttys[ind]} # Runs stty
LineDisc=$?
istrue debug &&
print -u2 "ChkLineDisc ${ttys[ind]} returned $LineDisc"
if [ LineDisc -eq 255 ]; then
istrue debug && print -u2 "$deb No line discipline; skipping."
minutes=0
# Set minutes to the lesser of access time & mod time.
elif [ $atime -le $mtime ]; then
istrue debug && print -u2 "$deb atime <= mtime; using atime"
minutes=atime
else
istrue debug && deb="${deb}mtime < atime; "
# If access_only, use access time regardless...
if istrue access_only; then
# unless modify_backup, in which case the modify time
# should be used if a process on the tty has /dev/tty
# open or the event driver is being used
if istrue modify_backup; then
if Not_devtty_user ${ttys[ind]} && [ LineDisc -ne 2 ];
then
istrue debug &&
print -u2 "${deb}trusting/using atime"
minutes=atime
else # /dev/tty open or event driver in use
istrue debug &&
print -u2 "${deb}not trusting atime; using mtime"
minutes=mtime
fi
else
istrue debug && print -u2 "${deb}using atime anyway"
minutes=atime
fi
else
istrue debug && print -u2 "${deb}using mtime"
minutes=mtime
fi
fi
TTYIdle_ret=minutes
}
# Put a list of all pids that have /dev/tty open in global var devttyUsers,
# in the form of a ksh pattern.
function devtty {
typeset IFS
set -- $(/etc/fuser /dev/tty 2>/dev/null)
# Make devttyUsers a ksh pattern
IFS=\|
devttyUsers="@($*)"
devttyDone=1
}
# Not_devtty_user: check whether a process that has /dev/tty open is running on
# the named tty & is not stopped or a zombie
# Usage: Not_devtty_user ttyname
# Global vars: TTYNames[], PIDs[], devttyUsers, ttyprocsDone, devttyDone
function Not_devtty_user {
typeset tty=$1
typeset -i i=0
# Run ttyprocs to get a list of all of the procs on this tty.
isfalse ttyprocsDone && ttyprocs ${ttys[*]}
isfalse devttyDone && devtty
while [ -n "${TTYNames[i]}" ]; do
if [[ ${TTYNames[i]} = $tty && ${ProcStates[i]} != [ZT] ]] &&
eval [[ ${PIDs[i]} = $devttyUsers ]]
then
istrue debug &&
print -u2 "Process ${PIDs[i]} has /dev/tty open."
return 1
fi
let i+=1
done
return 0
}
# NotSaveProc: check whether a protected process is running on a tty.
# Usage: NotSaveProc ttyname process-pattern
# Returns success if no protected processes are on the tty.
# Global vars: ProcNames[], TTYNames[]
function NotSaveProc {
typeset tty=$1 saveprocs=$2
typeset -i i=0
[ "$saveprocs" = \* ] && return 1
isfalse ttyprocsDone && ttyprocs ${ttys[*]}
while [ -n "${TTYNames[i]}" ]; do
if [[ ${TTYNames[i]} = $tty && ${ProcStates[i]} != [ZT] ]] &&
eval [[ ${ProcNames[i]} = "$saveprocs" ]]
then
istrue debug &&
print -u2 "Protected process \"${ProcNames[i]}\" running on $tty"
return 1
fi
let i+=1
done
return 0
}
# ChkLineDisc: check what line discipline a tty is using.
# Usage: ChkLineDisc ttyname
# Return value: Line discipline number,
# or 255 if no line disc (slip/ppp/event/etc. is being used.
# ptys present a problem because if the master is not open, an attempt to open
# the slave will hang. Generally, the utmp entry will be removed at the time
# the master side is closed, but a case where it isn't is guaranteed to cause
# nidleout to hang until killed, since the master can't be opened again while
# anything (like nidleout) is waiting on open.
# Therefore, the global nocheckdisc may be set to a pattern describing ttys
# that this function should not attempt to open. 0 is always returned instead.
function ChkLineDisc {
typeset stty_out line tty=$1
typeset -l ltty=$tty # Use lower case port for non-blocking sio open
typeset -i retval
if [ -n "$nocheckdisc" ] && eval [[ \"$tty\" = "$nocheckdisc" ]]; then
istrue debug &&
print -u2 "$tty matches \"$nocheckdisc\"; not checking line disc."
return 0
fi
# Some serial drivers will not allow two devices for the same port to be
# opened at the same time. Use ditty if available for Digiboard ports;
# it will do a non-blocking open. Only have the tty name to go on to
# determine if it's a Digi port...
if [[ "$tty" = tty[A-Z][0-9][0-9] ]] && type ditty > /dev/null; then
stty_out="$( (ditty -a -n /dev/$tty) 2>/dev/null)"
else
stty_out="$( (stty -a < /dev/$ltty) 2>/dev/null)"
fi
# Lack of any line discipline indicates that slip or ppp is being used.
# Line discipline 2 is the event driver.
if [ -z "$stty_out" ]; then
print -u2 -- "ChkLineDisc: could not get termio parameters for $ltty"
return 0
fi
if [[ "$stty_out" != *"line ="* ]]; then
istrue debug && print -u2 "$tty has no line discipline."
return 255
else
line=${stty_out#*line = }
line=${line%%[!0-9]*}
if istrue debug; then
case $line in
0) ;;
2) print -u2 "$tty is using line discipline $line (event driver)."
;;
*) print -u2 "$tty is using line discipline $line.";;
esac
fi
return $line
fi
}
# Put the host that pty user is logged in from in Hosts[ptynum]
# Expects nwho output to be in these forms:
# mickah ttyp7 Jul 23 12:42 (friday.echo.com)
# spcecdt ttyp9 Jul 23 12:38 (deeptht.armory.com:0)
function FindHosts {
typeset IFS=' ():' user pty d1 d2 h1 h2 host
unset Hosts[*]
nwho | while read user pty d1 d2 h1 h2 host; do
host=${host#\(} # having ( in IFS doesn't seem to work
SetHost "$pty" "$host"
done
}
# Usage:
# SetHost tty hostname
# Put the host that pty user is logged in from in Hosts[ptynum]
# Note: the indexing used here assumes that pty names are decimal numbered.
# Could convert non-decimal numbers, but then the decimal ones would have
# different values. Should really use minor # but too much trouble.
# So non-decimal ptys are ignored.
function SetHost {
typeset pty=$1 host=$2 ptynum
if [[ "$pty" = ttyp* && -n "$host" ]]; then
ptynum=${pty#ttyp}
if [[ "$ptynum" = +([0-9]) ]]; then
Hosts[$ptynum]=$host
((debug)) &&
print -u2 "User $user on $pty is logged in from $host"
else
((debug)) && print -u2 "Warning: non-decimal pty name '$pty'."
fi
fi
}
# Under 3.2v4:
# Already open
# Open of Neither Master&slave Master only Slave only
# master Succeeds Fails Fails Fails
# slave Hangs Succeeds Succeeds Hangs
#
# Usage: idlettys access_only modify_backup
# idlettys prints one line.
# The first word on the line is the number of minutes until the tty that
# is closest to reaching its idle limit without having reached it will
# reach that limit.
# If there are no ttys idle for a minute or more that have not reached
# their limit, the number printed will be the smallest rule time.
#
# The rest of the words are the indexes in ttys[] of lines that match
# a rule.
#
# A line is idle for as long as it has not been read from or written too.
#
# Global vars:
# idlettys uses ttys[], ttytimes[], users[], idlettys_ilist, and
# idlettys_mintime
# R_user, R_tty, R_procs, R_rhost, and R_time
# MinTime is the shortest idleout time for any rule.
typeset -i ttytimes idlettys_mintime
function idlettys {
typeset access_only=$1 modify_backup=$2
typeset tty user rhost
# mindiff is initialized to MinTime, so that even if all idle times are 0,
# we will wake up after MinTime.
typeset -i i=0 j diff mins time MaxTime Did_nwho=0 ptynum
idlettys_ilist=
idlettys_mintime=MinTime
$utmpx && Did_nwho=1
while [ i -lt ${#ttys[*]} ]; do
tty=${ttys[i]}
user=${users[i]}
j=0
# find the highest-time rule that matches the idle user
MaxTime=0
while [ j -lt NumRules ]; do
if eval [[ \$user = "${R_user[j]}" '&&' \$tty = "${R_tty[j]}" ]]
then
rhost=${R_rhost[j]}
if [[ "$tty" = ttyp+([0-9]) && $rhost != \* ]]; then
((Did_nwho)) || { FindHosts; Did_nwho=1; }
ptynum=${tty#ttyp*}
eval [[ \"\${Hosts[ptynum]}\" = $rhost ]] || {
let j+=1
continue
}
fi
# Make sure no protected processes are running on the tty
if NotSaveProc "$tty" "${R_procs[j]}"; then
let j+=1
continue
fi
mins=${R_time[j]}
((debug)) &&
print -u2 "$user on $tty matches rule $j (itime=$mins)."
if [ mins -eq 0 ]; then
MaxTime=0
break
fi
[ mins -gt MaxTime ] && MaxTime=mins
fi
let j+=1
done
((debug)) && print -u2 "$user on $tty: idleout time = $MaxTime"
# If a rule said this login should not be idled out, or there was no
# rule for the login, skip it
if [ MaxTime -gt 0 ]; then
# Find the allowed idle time for this tty
TTYIdle $i $MaxTime access_only=$1 modify_backup=$2
time=TTYIdle_ret
((debug)) && print -u2 "ttytimes[$i]=$time"
ttytimes[i]=time
((debug)) && print -u2 "idle time for $tty: $time"
if [ time -ge MaxTime ]; then
idlettys_ilist="$idlettys_ilist $i"
else
diff=MaxTime-time
[ diff -lt idlettys_mintime ] && idlettys_mintime=diff
fi
fi
let i+=1
done
((debug)) && print -u2 \
"In idlettys, ttytimes indexes are <$idlettys_ilist>.
Values are <${ttytimes[*]}>"
}
# Usage: ttyprocs tty ...
# ttyprocs sets the arrays ProcNames[], PIDs[], TTYNames[], and ProcStates[]
# to the real names, process ids, controlling ttys, and states of all
# of the processes attached to any of the ttys given as arguments.
# Indexes start at 1.
# hmm, this doesn't seem to actually pay attention to its ttyname arguments...
# Under 3.2v4 ps -l output has this form:
# F S UID PID PPID C PRI NI ADDR1 ADDR2 SZ WCHAN TTY TIME CMD
# 20 S 0 1618 1615 3 30 20 9d4 69d 88 f00ef8a0 p0 0:10 ksh
# 20 O 0 9925 1618 36 78 20 366 30d 84 p0 0:00 ps
# Under 5.0 it has this form:
# F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
# 0 S 0 26072 1231 0 73 0 fb11d218 144 fb11d218 ttyp17 00:00:00 te
# 20 O 0 5059 26073 14 60 0 fb11d370 192 - ttyp17 00:00:00 ps
function ttyprocs {
typeset pid header f s uid pid ppid c pri ni addr args
typeset -i i=0
((debug)) && print -u2 "ttyprocs args: $*"
unset PIDs ProcNames TTYNames ProcStates
ps_t -l "$@" | {
read header
while read f s uid pid ppid c pri ni addr args; do
((debug)) &&
print -u2 "From ps_t -l: $f|$s|$uid|$ppid|$c|$pri|$ni|$addr|$args"
# zombies lack many fields and are not important, so skip them.
[ "$2" = Z ] && continue
ProcStates[i]=$s
PIDs[i]=$pid
# Remaining fields are ADDR2 SZ WCHAN TTY TIME CMD
# or SZ WCHAN TTY TIME CMD
# addr2 and/or wchan may be missing and/or size may merge into
# addr2. Deal with this by shifting until TIME is in field 2
set -- $args
while [[ $# -gt 0 && "$2" != ?([0-9][0-9]:)+([0-9]):[0-5][0-9] ]]
do
shift
done
[ $# -lt 3 ] && continue
[[ "$1" = tty* ]] && TTYNames[i]=$1 || TTYNames[i]=tty$1
shift 2
ProcNames[i]="$*"
((debug)) && print -u2 \
"line $((i+1)) from ps -l: State=$s PID=$pid TTY=${TTYNames[i]} Proc=$*"
let i+=1
done
}
((debug)) && print -u2 "ttyprocs read $i non-zombie process lines."
ttyprocsDone=1
}
# Usage: ps_t ps-options tty ...
# Pass a null argument if no ps-options
# Equivalent to ps -t, except that tty args are given separately
# Knows where to find tty field for default, -l, and -f output.
function ps_t {
typeset TTYPat line IFS psopts=$1
shift # get rid of ps options
# ps -t has a 20 tty limit but is faster
if [ $# -le 20 ]; then
((debug)) && print -u2 "$0: running /bin/ps $psopts -t $*"
# Redir from /dev/null in case run with no fd 0, which breaks protlib.
/bin/ps $psopts -t "$*" < /dev/null || ErrMsg ps failed: errno $ERRNO
return 0
fi
# Make TTYPat a ksh pattern
IFS=\|
TTYPat="*($*)"
IFS="
"
# This & the for loop are much faster than a read loop
((debug)) && print -u2 "$0: running /bin/ps $psopts -e"
set -- $(/bin/ps $psopts -e < /dev/null || ErrMsg ps failed: errno $ERRNO)
IFS="
"
# print header & get rid of it
print -- "$1"
shift
((debug)) &&
print -u2 "$0: read $# lines from ps. Comparing ttys to $TTYPat"
# eval is neccessary to make TTYPat be treated as a regexp.
# repeated eval is expensive, so eval the whole loop, quoting
# everything except TTYPat.
eval '
for line in "$@"; do
set -- $line
case "$psopts" in
-f) tty=$6;;
-l) tty=${12};;
*) tty=$2;;
esac
[[ $tty != tty* ]] && tty=tty$tty
((debug)) && print -u2 "$0: tty = $tty"
[[ $tty = '"$TTYPat"' ]] && print -- "$line"
done
'
}
# Usage: ttykill ttyname idlemin [user]
# Global variables: TTYNames[], PIDs[], Signals[], KillSleep[]
function ttykill {
typeset tty=$1 idlemin=$2 pids signal
typeset -i i=0 sleep
while [ -n "${TTYNames[i]}" ]; do
((debug)) && print -u2 "$0: TTYNames[$i]=${TTYNames[i]}"
[ ${TTYNames[i]} = "$tty" ] && pids="$pids ${PIDs[i]}"
let i+=1
done
((debug)) && print -u2 "pids for $tty: $pids"
[ -z "$pids" ] && return 0
i=0
while [ i -lt ${#Signals[*]} ]; do
signal=${Signals[i]}
if ((debug)); then
if [[ "$signal" = +([0-9]) ]]; then
print -u2 "Sending signal $signal to $pids"
else
print -u2 "Sending SIG$signal to $pids"
fi
fi
kill -$signal $pids 2>|/dev/null
sleep=${KillSleep[i]}
let i+=1
if [ i -lt ${#Signals[*]} -a sleep -gt 0 ]; then
((debug)) && print -u2 "Sleeping for $sleep seconds."
sleep $sleep
fi
done
}
# Usage: idleout <access_only> <modify_backup> <action> <logfile>
# Sends a SIGHUP to processes attached to ttys that match a rule.
# A number is printed that is the minimum number of minutes until another
# tty could match a rule.
function idleout {
typeset access_only=$1 modify_backup=$2 action=$3 logfile=$4
typeset tty idlei ttyNames
typeset -i sleeptime idlemin i
if istrue debug; then
CurTime
print -u2 "Time: $CurTime_ret"
fi
ttyprocsDone=0
devttyDone=0
# Make ttys[], acctimes[], modtimes[], users[]
MakeTTYList
idlettys $access_only $modify_backup
sleeptime=idlettys_mintime
idlei=$idlettys_ilist
((debug)) && print -u2 \
"In idleout, ttytimes values are <${ttytimes[*]}>"
if [ -z "$idlei" ]; then
print $sleeptime
return 0
fi
for i in $idlei; do
ttyNames="$ttyNames ${ttys[i]}"
done
# Run ttyprocs to get a list of processes to kill for each tty.
isfalse ttyprocsDone && ttyprocs $ttyNames
istrue debug && print -u2 "ttys to idle out users on:$ttyNames"
# Do a ps on ttys for logfile before killing off processes...
if [ -n "$logfile" -a -w "$logfile" ]; then
{
date +"%y/%m/%d %T"
ps_t -f $ttyNames | tail +2
} >> $logfile
fi
for i in $idlei; do
tty=${ttys[i]} idlemin=${ttytimes[i]} user=${users[i]}
if [ idlemin -le 0 ]; then
ErrMsg \
"Error: $user idle $idlemin minute(s) on $tty; trying to idleout?"
ErrMsg "ttytimes index is <$i>. ttytimes[$i] is <${ttytimes[i]}>."
ErrMsg \
"ttytimes indexes are <$idlei>. ttytimes values are <${ttytimes[*]}>"
continue
fi
case $action in
warn) iwrite $tty \
"\rYou have been idle for more than $idlemin minutes.
\rPlease log out if you aren't going to do anything." $user
;;
kill) iwrite $tty \
"\rYou have been idle for more than $idlemin minutes,
\rso you're being logged off. You have one minute to clean up. Goodbye!" $user
;;
esac
done
((debug)) && print -u2 "Action: $action"
if [ $action = kill ]; then
sleep 60 # Give the user the promised minute to clean up.
((debug)) && print -u2 "Grace period expired; killing..."
for i in $idlei; do
tty=${ttys[i]} idlemin=${ttytimes[i]} user=${users[i]}
((debug)) && print -u2 "+ttykill $tty $idlemin $user"
ttykill $tty $idlemin $user
done
fi
print $sleeptime
}
# MakeIdleArrs: convert a list of user=time specs to rules.
# Usage: MakeIdleArrs pattern=time [ pattern=time ... ]
# MakeIdleArrs returns 0 on success, 1 on failure
function MakeIdleArrs {
for arg; do
MakeRule "user=${arg%=*}:time=${arg#*=}" || return 1
done
return 0
}
function main {
typeset -i sleeptime
while :; do
idleout $access_only $modify_backup $action $logfile |
read sleeptime
istrue debug && print -u2 "$tl_name: Sleeping for $sleeptime minute(s)"
if [ sleeptime -eq 0 ]; then
print -u2 "$tl_name: got 0 sleeptime!"
sleeptime=1
fi
sleep $((sleeptime * 60))
done
}
# Usage:
# MakeRule rule
# rule is a set of colon-separated fields.
# Each field is of the form name=value
# Parses rules & stores them in global arrays
# R_user, R_tty, R_procs, R_rhost, R_time, and R_signals,
# starting with index 0.
# The smallest time greater than 0 is put in global MinTime.
# The number of rules is stored in global NumRules
# The return value is 0 on success, 1 if the rule was bad
typeset -i NumRules=0 MinTime=0
function MakeRule {
typeset OIFS IFS Rule=$1 field var value Fields
typeset -i Err=0 i=0 NumFields
((debug)) && print -u2 "rule: $Rule"
OIFS=$IFS
IFS=:
set -A Fields -- $Rule
IFS=$OIFS
R_user[NumRules]=\*
R_tty[NumRules]=\*
R_procs[NumRules]=\*
R_rhost[NumRules]=\*
R_time[NumRules]=
# R_signals[NumRules]=$signals
NumFields=${#Fields[*]}
while [ i -lt NumFields ]; do
field=${Fields[i]}
IFS==
set -- $field
IFS=$OIFS
var=$1 value=$2
case $var in
time)
if [[ "$value" != +([0-9]) ]]; then
print -u2 "$tl_name: Invalid time given in rule:\n$Rule"
Err=1
else
if [ $value -gt 1440 ]; then
print -u2 "Warning: time '$value' changed to 1 day (1440)."
value=1440
fi
R_time[NumRules]=$value
[[ (MinTime -eq 0 || $value -lt MinTime) && $value -gt 0 ]] &&
MinTime=$value
fi
;;
user|tty|procs)
eval R_$var[NumRules]=\$value
;;
rhost)
if ! $utmpx && [ Found_nwho -ne 0 ]; then
print -u2 \
"$tl_name: need either who -x or nwho if rhost param is used."
Err=1
fi
eval R_rhost[NumRules]=\$value
;;
*)
print -u2 \
"$tl_name: Invalid field '$var' assigned to in rule:\n$Rule"
Err=1
;;
esac
((i+=1))
done
if [ ${#R_time[*]} -le NumRules ]; then
print -u2 "$tl_name: No time given in rule:\n$Rule"
Err=1
fi
((Err)) || ((NumRules+=1))
return $Err
}
# Usage: ProcDefFile default-file-name
# Sets globals idletimes, log, ttys, saveprocs, siglist
# Uses globals NoRules and debug.
function ProcDefFile {
typeset DefFile=$1 var
typeset -l lowvar
typeset -i Err=0
[ ! -r $DefFile ] && return 0
while read line; do
case "$line" in
*([ ])|\#*) # comment/space
((debug)) && print -u2 "comment/space: $line"
;;
@(IDLETIMES|LOGFILE|TTYPAT|SAVEPROCS|NOCHECKDISC|SIGLIST)=*)
((debug)) && print -u2 "simple-var: $line"
# Assign these values carefully
var=${line%%=*}
lowvar=$var
# Assign values only if empty (not set on command line)
eval [ -z \"$"$lowvar"\" ] && eval $lowvar=\${line#$var=}
;;
[A-Z]*=*)
print -u2 \
"$tl_name: Value assigned to unknown variable in $DefFile:\n$line"
Err=1
;;
*=*)
((NoRules)) || MakeRule "$line" || Err=1
;;
*)
print -u2 "$tl_name: Bad line in $DefFile:\n$line"
Err=1
;;
esac
done < $DefFile
return $Err
}
typeset -i CurMinute_ret
function CurMinute {
(( CurMinute_ret=(SECONDS/60+StartTime) % 1440 ))
}
function CurTime {
typeset -Z2 Hr Min
CurMinute
((Hr=CurMinute_ret/60))
((Min=CurMinute_ret%60))
CurTime_ret=$Hr:$Min
}
# StartTime may be negative; doesn't matter if only CurMinute uses it.
typeset -i StartTime
function TimeInit {
typeset -i hr min
date "+%H %M" | read hr min
StartTime=hr*60+min-SECONDS/60
}
# Usage: MakeSigArrs siglist
typeset -i KillSleep
typeset -u Signals
function MakeSigArrs {
typeset -i i=0
typeset siglist=$1
OIFS=$IFS
IFS=,
set -- $siglist
IFS=$OIFS
while [ $# -ge 1 ]; do
Signals[i]=$1
shift
if [ $# -gt 1 ]; then
KillSleep[i]=$1 || {
print -u2 "$tl_name: Bad sleep time: $1. Exiting."
exit 1
}
shift
fi
((debug)) &&
print -u2 "siglist pair $i: signal=${Signals[i]} sleep=${KillSleep[i]}"
let i+=1
done
}
# Start of main program
set -o noglob
Err=0
DefFile=/etc/default/nidleout
typeset -i fgnd=0 access_only=0 modify_backup=0 debug=0 ttyprocsDone NoRules=0
typeset -i NoRun=0
action=kill
tl_name=${0##*/}
while getopts :D:t:i:l:s:S:aAdefhnpwx flag; do
case $flag in
a) access_only=1;;
A) access_only=1; modify_backup=1;;
d) action=log;;
D) nocheckdisc=$OPTARG;;
e) NoRun=1;;
f) fgnd=1;;
n) NoRules=1;;
p) nocheckdisc="ttyp*";;
w) action=warn;;
x) debug=1;;
t) ttypat=$OPTARG;;
s) saveprocs=$OPTARG;;
S) siglist=$OPTARG;;
i) idletimes=$OPTARG;;
l) logfile=$OPTARG
if ( >> $logfile ) 2>|/dev/null; then
:
else
echo "Cannot write to $logfile."
Err=1
fi;;
h) ${PAGER:-pg} <<END_HELP
Usage: $tl_name [-aAdefnpwxh] [-t<tty_pattern>] [-i<idle_times>] [-l<logfile>]
[-s<save_procs>] [-D<tty_pattern>]
$tl_name logs out idle users. Whether a user is idle or not is determined
by examining the last-modified and last-accessed time on the user's tty. The
modify time is updated each time that the terminal is written to. The access
time is updated each time the terminal is read from. The terminal is read from
each time that it returns a line or character of input (depending on whether it
is in canonical or non-canonical mode) to a process that has requested input.
These are imperfect indicators of user activity; there is no facility for
finding out when the last actual input from a user was received.
Most utilities read from and write to the tty in such a way that either
the access or modify time or both are updated. However, there are some
situations in which the modify and/or access time on a tty are not updated even
though a user is active. The most common are: use of /dev/tty for input and/or
output, use of the event driver, extended interaction with the tty driver,
output via direct video access, use of utilities that do not read input for an
extended period, and complete bypass of the tty mechanism. Options are provided
to deal with most of these situations. The following explanations will help
you recognize them.
Some utilities, like pg(C) and less, access the user's tty input by
reading the device /dev/tty because the process' standard input may have been
redirected. In this case, the access time on /dev/tty will be updated instead
of the access time on the "real" tty. Similarly, some utilities write to
/dev/tty because they are liable to be invoked with their output redirected;
these will not update the modify time of the real tty.
Any process that uses the event driver will prevent the access time on
the tty from being updated, because the event driver bypasses the normal read
mechanism. Utilities that use a mouse, like usemouse(C) and the console X
server, are examples of utilities that use the event driver.
Many utilities rely on the line discipline for echoing keyboard input. The
line discipline does not update the modify time on the tty when it echos input,
so it will appear that nothing is being written to the user's tty when in such
a utility (mail(C), ex(C), etc.) Xon/xoff flow control are also handled within
the tty driver. If a user is reading a large file and pausing the output with
xoff and xon (^S/^Q), the access time will not be updated even though the user
is typing the xon/xoff characters.
Applications that are running on the console and are using direct video
memory access (VP/ix, Merge, and applications that produce graphical displays,
like the console X server) will not update the modify time, because they don't
write to the tty.
Some applications do processing for extended period, and do not read from
the tty while doing this processing. Even though a user may be typing, and
depending on the utility the input may be being echoed by the line discipline,
since no read is being done the user becomes idle. An example of this is ftp;
during a long transfer a user will become idle.
Finally, some processes divert serial IO at a low level within the kernel.
Examples of this are the SLIP and PPP drivers. These update neither the access
nor modify time. Idleout never acts on ttys that have no line discipline,
which indicates that a driver of this type in in use.
When $tl_name is started, it goes into the background and begins checking
the times on users' ttys. If a user has been idle for more than the specified
time, a message is written to the user's tty warning that the user is about to
be logged off. After ten seconds has passed, all processes attached to the tty
that the user is logged in on are sent a terminate signal. This allows
processes that catch the terminate signal to clean up. After another ten
seconds has passed, all of the remaining processes are sent a hangup signal, so
that processes that ignore the terminate signal but pay attention to the hangup
signal can clean up. After another ten seconds has passed, all of the
processes are sent a kill signal, which cannot be ignored.
Options:
-l sets the name of the log file to which one line is written, in the
format of ps -f, for each process running on the user's tty at the time the
user is logged out. The default is $DefFile. The log file will
NOT be written to unless it already exists at the time $tl_name is run. If an
explicit log file name is given but the file does not already exist, $tl_name
will abort with an error.
If -d or -w is given, the processes running on an idle tty are logged in
the logfile but the user is not logged out. If a user remains idle, the user
will probably be logged more than once. If -w is given, the user is warned,
but is still not logged out.
-e causes $tl_name to exit immediately after all initialization is
complete (for testing of rules, etc.) Usually used with -x.
-f causes $tl_name to remain in the foreground.
-a causes only the access time on a tty to be used for the idle time. The
default is to consider the time since the most recent of the access and modify
times to be the idle time. In general, the access time (which is the last time
that input from the user was acted on) is a better indicator of user activity
than the modify time. For example, some utilities regularly write to a tty
even when they are not being used. If a tty with such a utility running on it
should be considered idle if no one is typing at it, use -a. Note that, as
described above, some processes do not update the access time on the tty even
when they are reading input from a terminal. If -a is given, users using these
utilities are liable to be incorrectly logged out. Use -s and/or -A to prevent
this.
-A is like -a, except that if the tty is not using the standard line
discipline, or if a process on the tty has /dev/tty open and is not stopped,
the modify time is used instead of the access time. In most cases this is a
better option to use than -a. However, even -A will not help the situation
where a user is using xon/xoff to read a large file, or where a user has begun
a lengthy operation that will not read any more input from the user until it
has completed.
-n causes any rules given in $DefFile to be ignored. Simple
assignments are still read.
-D prevents $tl_name from checking the line discipline in effect on any
ttys that match the pattern given. To prevent the line discpline from being
checked for any tty, use -D '*' (a pattern no tty will match). -D can be used
if there are ttys that $tl_name should not attempt to open for reading (as is
neccessary to check the line discipline).
-p prevents $tl_name from attempting to determine what line discipline
is in use on ptys. Due to pty open behaviour in some releases of the operating
system, in some circumstances an attempt at opening the slave side will hang.
Note that if -p is given, users using an alternate line discipline on a pty
are liable to be incorrectly idled out. -p is equivalent to -D 'ttyp*'
-s is used to give a shell pattern that should match any processes that
should not be idled out. If the name of any process running on a tty matches
the pattern given, the user on that tty will not be idled out (no processes on
the tty will be killed). This would typically be used to save processes that
do not update the access or modify time on a tty, or, if the -a flag is being
used, processes which may update the modify time but not the access time.
Example pattern: "@(pg|sx|sz|sb)". If a pattern is given on the command
line, it should be protected from the shell with quotes.
-i is used to specify the number of minutes a user may be idle before
being logged out. Its format is: userpat=minutes[:userpat=minutes...] Each
colon-separated specification gives a shell pattern to compare user names to,
and the number of minutes that users whose login names match that pattern are
allowed to be idle. An example of a simple specification is "*=15". This
specifies that the idleout time for all users is 15 minutes. An example of a
more complex specification is "@(root|backup)=240:spcecdt=60:*=15". This
specifies that root and backup have an idleout time of 4 hours, spcecdt has an
idleout time of 1 hour, and everyone else has an idleout time of 15 minutes.
User names are compared to patterns in the order they are given, and the time
for the first pattern that a user name matches is used. The default is "*=60".
A time of 0 minutes means that no idleout should be done. If a pattern is
given on the command line, it should be protected from the shell with quotes.
-t is used to give a shell pattern that determines which ttys $tl_name
acts on. It should match the file part of the tty name, without the leading
/dev. Use "*" to match all ttys. "*[A-Z]*" would typically be used to only
match modem ttys. "!(ttyp*)" will match all real ttys, but allow users on
psuedo-ttys to be idle. The default is "*".
-S is used to give a list of signals to send to processes to kill them,
and periods to sleep between sending the signals. The value has the form:
<signal>[,<sleeptime>]...
<signal> is either a signal number or the name of the signal without the
leading 'SIG', e.g. KILL. <sleeptime> is the period in seconds to sleep after
sending the preceding signal before sending the next signal. The default is
INT,10,HUP,10,KILL
This causes a SIGINT to be sent, followed by a sleep of 10 seconds, then a
SIGHUP to be sent, followed by a sleep of 10 seconds, and finally a SIGKILL to
be sent.
-x turns on debugging output. It is written to standard error.
More complex actions can be specified in the specification file
/etc/default/nidleout. Lines in this file that are blank or begin with # are
ignored. Other lines have one of two forms, either
VARNAME=value
or
field=value:field=value:...
The first form is used to assign values to certain of the parameters that can
be given on the command line. The tty pattern, idle times, log file, pattern
to match ttys that should not have their line discipline checked, and saved
processes pattern may be set by assigning values to the variables TTYPAT,
IDLETIMES, LOGFILE, NOCHECKDISC, SAVEPROCS, and SIGLIST
respectively. For example:
IDLETIMES=*=15
TTYPAT=*[A-Z]*
NOCHECKDISC=ttyp*
SAVEPROCS=@(pg|more|ftp)
will set the idle time, tty pattern, and save processes pattern, and leave LOG
set to the default. Values given on the command line override any set in the
default file.
The second form is used specify idleout actions that are dependent on more
than one parameter. Each line describes an idleout rule. The rule consists
of various parameters that must match a user login in order for that rule to
match the login. A rule matches only if all of the parameters given match.
The idleout time used for each login is the highest time of any rule that
matches the login. The parameter fields are user, tty, procs, and rhost. The
value of each field is given in ksh pattern syntax. Any parameters not
assigned a value in a rule default to '*', so that those parameters always
match. The 'time' field gives the idleout time for a rule; every rule must
have a time field. Note that values higher than 1440 minutes (1 day) are
rounded down to that, since that is the highest value 'who' will report. A
time field of 0 means that logins matching this rule should not be idled out.
The user and tty fields are matched against the user's login name and the
tty the user is on. The procs field will match if any process running on the
tty matches its value. The rhost field is matched against the host that a
remote user is logged in from, for rlogin and telnet connections. It will be
matched against the domain name of the host if it has one, else its IP address.
Use of this parameter requires either that the 'who' command implements the -x
option (print remote hostnames), or the existence of the 'nwho' program, and is
only checked for pseudo-ttys regardless of the tty parameter.
Example:
# These users are allowed to be idle for 60 minutes on a modem.
user=@(spcecdt|root|pax):tty=tty3[A-H]:time=60
# These users are allowed to be idle for 1 day on a pty.
user=@(spcecdt|root|filbo):tty=ttyp+([0-f]):time=1440
# Users coming from any host in armory.com will never be idled out.
tty=ttyp+([0-f]):rhost=*.armory.com:time=0
# These processes read & write /dev/tty, or don't read input during processing.
tty=tty@(3[A-H]|p+([0-f])):procs=@(pg|rb|rz|rx|sb|sz|sx|ftp):time=240
# Idle out general modem users in 10 minutes.
tty=tty3[A-H]:time=10
# Idle out net users in 14 hours.
tty=ttyp+([0-f]):time=840
Note that the simpler idleout specifications (IDLETIMES, etc.) other than
NOCHECKDISC would not normally be used in conjunction with rules. The -n
option can be used to skip any rules in $DefFile so that simple specifications
can be given on the command line. They are internally converted into rules as
follows: Each field of IDLETIMES becomes a rule with only user and time set.
TTYPAT and SAVEPROCS become rules with only tty or procs set, respectively, and
with time set to 0; in the case of TTYPAT anything except the tty pattern is
matched.
$tl_name is a Korn shell script. If it is not invoked from a Korn shell,
and hashpling is not enabled on the system, it must be run by the Korn shell
explicitly. If $tl_name is put in /usr/bin, use the command:
/bin/ksh $0 <arguments...>
END_HELP
exit 0;;
?) echo "$OPTARG: unknown flag. Use -h for help."
Err=1;;
:) echo "$OPTARG: option requires a value. Use -h for help."
Err=1;;
esac
done
# External commands used:
# /bin/ksh awk date who stty ps sleep tail fuser nwho
export PATH=/bin:/usr/bin:/usr/local/bin
if who -x >/dev/null 2>&1; then
utmpx=true
Found_nwho=1
else
utmpx=false
whence nwho > /dev/null
Found_nwho=$?
fi
ProcDefFile $DefFile || Err=1
if [ $Err = 1 ]; then
echo "Exiting."
exit 1
fi
# Convert simple specs to arrays
OIFS=$IFS
IFS=:
MakeIdleArrs $idletimes || exit 1
IFS=$OIFS
[ -n "$ttypat" ] && MakeRule "tty=!($ttypat):time=0"
[ -n "$saveprocs" ] && MakeRule "procs=$saveprocs:time=0"
# If no inclusion rules at all given, set this one
[ MinTime -eq 0 ] && MakeRule "time=60"
[ -z "$siglist" ] && siglist=INT,10,HUP,10,KILL
MakeSigArrs "$siglist"
if ((debug)); then
typeset -i i=0
while [ i -lt NumRules ]; do
print -u2 "Rule $i: user=${R_user[i]} tty=${R_tty[i]}"\
" procs=${R_procs[i]} rhost=${R_rhost[i]} time=${R_time[i]}"
((i+=1))
done
print -u2 "nocheckdisc=$nocheckdisc"
print -u2 "siglist=$siglist"
fi
trap "" HUP
if [ -n "$logfile" ]; then
echo "$tl_name: logfile is $logfile. $NumRules rule(s)."
else
echo "$tl_name: $NumRules rule(s)."
fi
TimeInit
((NoRun)) && exit 0
if [ $fgnd = 1 ]; then
main
else
main &
fi